﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;



namespace ScriptEase
{
    /// <summary>
    /// Observes a list of files for changes.
    /// </summary>
    /// <remarks>
    /// Files can be registered (<see cref="RegisterFile"/>) and 
    /// unregsitered (<see cref="UnRegisterFile"/>) for observation as needed.<br/>
    /// Client can subscribe for file change notifications: <see cref="Subscribe"/>.
    /// </remarks>
    public class FileObserver : IDisposable
    {
        private readonly List<FileChangedHandler> _notificationSubscriptions;
        private readonly Dictionary<string, FileSystemwatcher2> _registeredFiles;   // key: FullPath

        public delegate void FileChangedHandler( object sender, FileChangedEventArgs e);



        /// <summary>
        /// Initializes a new instance of the <see cref="FileObserver"/> class.
        /// </summary>
        public FileObserver()
        {
            _registeredFiles = new Dictionary<string, FileSystemwatcher2>(StringComparer.OrdinalIgnoreCase);
            _notificationSubscriptions = new List<FileChangedHandler>();
        }



        private void fileChangedDelayed(object sender, FileSystemEventArgs e)
        {
            Trace.Default.Debug("File {0}: {1}", e.ChangeType, e.Name);
            if (e.ChangeType != WatcherChangeTypes.Changed) return;
            if (!_registeredFiles.ContainsKey(e.FullPath)) return;

            notifySubscribers( e.FullPath);
        }



        public void Subscribe(FileObserver.FileChangedHandler subscriber)
        {
            _notificationSubscriptions.Add(subscriber);
        }


        public void UnSubscribe(FileObserver.FileChangedHandler subscriber)
        {
            _notificationSubscriptions.Remove(subscriber);
        } 
        
        public void UnSubscribeAll()
        {
            var subscriptions = _notificationSubscriptions.ToArray();
            foreach (FileChangedHandler subscriber in subscriptions)
            {
                _notificationSubscriptions.Remove(subscriber);
            }
        }


        public void RegisterFile(string fullPath)
        {
            if (!Path.IsPathRooted(fullPath)) throw new ApplicationException("Cannot register a relative path!");
            if (_registeredFiles.ContainsKey(fullPath)) return;

            Trace.Default.Debug("Register file notification {0}", fullPath);

            FileSystemwatcher2 fileSystemWatcher = new FileSystemwatcher2(fullPath);
            fileSystemWatcher.FileChangedDelayed += new FileSystemEventHandler(fileChangedDelayed);
            _registeredFiles.Add(fullPath, fileSystemWatcher);
        }


        public void UnRegisterFile(string fullPath)
        {
            if (!Path.IsPathRooted(fullPath)) throw new ApplicationException("Cannot unregister a relative path!");
            if (!_registeredFiles.ContainsKey(fullPath)) return;

            Trace.Default.Debug("UnRegister file notification {0}", fullPath);

            FileSystemwatcher2 fs2 = _registeredFiles[fullPath];
            fs2.FileChangedDelayed -= new FileSystemEventHandler(fileChangedDelayed);
            fs2.Dispose();
            _registeredFiles.Remove(fullPath);
        }



        public void UnRegisterAll()
        {
            var keys = _registeredFiles.Keys.ToArray(); // use array, otherwise the collection would be modified by unregistration
            foreach (string key in keys)
            {
                this.UnRegisterFile(key);
            }
        }



        private void notifySubscribers(string fullPath)
        {
            FileChangedEventArgs e = new FileChangedEventArgs(new FileInfo(fullPath));
            foreach (var subscription in _notificationSubscriptions)
            {
                subscription(  this, e);
                if (e.Cancel) break;
            }
        }



        private void disposeManaged()
        {
            this.UnSubscribeAll();
            this.UnRegisterAll();
        }



        #region IDispose


        public void Dispose()
        {
            dispose(true);
            GC.SuppressFinalize(this);
        }



        private void dispose(bool disposing)
        {
            if (!this._disposed)
            {
                if (disposing)
                    disposeManaged();
                //CloseHandle(handle);
            }
            _disposed = true;
        }



        private bool _disposed;


        #endregion
    }
}